﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Ebt.Linq
{
	/// <summary>
	/// The base class for <see cref="IQueryable"/> implementations.
	/// </summary>
	/// <typeparam name="TItem">The type of the content of the data source.</typeparam>
	public class Query<TItem> : IOrderedQueryable<TItem>
	{
		private readonly IQueryProvider _provider;
		private readonly Expression _expression;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="provider"></param>
		public Query(IQueryProvider provider)
		{
			if (provider == null)
				throw new ArgumentNullException("provider");

			_provider = provider;
			_expression = Expression.Constant(this);
		}

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="provider"></param>
		/// <param name="expression"></param>
		public Query(IQueryProvider provider, Expression expression)
		{
			if (provider == null)
				throw new ArgumentNullException("provider");
			if (expression == null)
				throw new ArgumentNullException("expression");
			if (!typeof (IQueryable<TItem>).IsAssignableFrom(expression.Type))
				throw new ArgumentOutOfRangeException("expression");

			_provider = provider;
			_expression = expression;
		}

		Expression IQueryable.Expression
		{
			get { return _expression; }
		}

		Type IQueryable.ElementType
		{
			get { return typeof (TItem); }
		}

		IQueryProvider IQueryable.Provider
		{
			get { return _provider; }
		}

		public IEnumerator<TItem> GetEnumerator()
		{
			// TODO Quick fix for "MultipleActiveResultSets is not supported": ToList()

			var providerResult = _provider.Execute(_expression);
			return FixCollectionType(providerResult).ToList().GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			// TODO Quick fix for "MultipleActiveResultSets is not supported": ToList()

			var list = new ArrayList();
			foreach (var item in (IEnumerable)_provider.Execute(_expression))
			{
				list.Add(item);
			}
			return list.GetEnumerator();

		} 

		private static IEnumerable<TItem> FixCollectionType(object collection)
		{
			if (collection == null)
				yield break;

			var items = collection as IEnumerable;
			if (items == null)
				throw new NotSupportedException("The result set has an unexpected type: " + collection.GetType().FullName);

			foreach (var item in items)
			{
				TItem entity;
				try
				{
					entity = (TItem)item;
				}
				catch (InvalidCastException castException)
				{
					throw new NotSupportedException("The result set has an unexpected type of elements.", castException);
				}
				yield return entity;
			}
		}
	}
}